/*
 * Sistema.cpp
 *
 *  Created on: Nov 11, 2013
 *      Author: damian
 */

#include <sys/types.h>
#include <sys/stat.h>
#include "Sistema.h"
#include "../CargaMasiva/Cargar_Archivo.h"


Sistema::Sistema() : sys_files_path(INDICES_PATH) {

	// crea la carpeta si no existe
	struct stat st = {0};
	if (stat(sys_files_path.c_str(), &st) == -1) {
		mkdir(sys_files_path.c_str(), 0700);
	}

	hashUsuarios = new HashManager(sys_files_path+"usuarios", sys_files_path+"hashUsuarios", BLOQUE_SIZE);
	hashServicios = new HashManager(sys_files_path+"servicios", sys_files_path+"hashServicios", BLOQUE_SIZE);
	hashConsultas = new HashManager(sys_files_path+"consultas", sys_files_path+"hashConsultas", BLOQUE_SIZE);
	hashCategorias = new HashManager(sys_files_path+"categorias", sys_files_path+"hashCategorias", BLOQUE_SIZE);
	hashCotizaciones = new HashManager(sys_files_path+"cotizaciones", sys_files_path+"hashCotizaciones", BLOQUE_SIZE);

	indiceServiciosPorCategoria = new ArbolBMas(sys_files_path+"IndiceServiciosPorCategoria.dat", BLOQUE_SIZE);
	indiceServiciosPorProveedor = new ArbolBMas(sys_files_path+"IndiceServiciosPorProveedor.dat", BLOQUE_SIZE);
	indiceConsultasPorServicio = new ArbolBMas(sys_files_path+"IndiceConsultasPorServicio.dat", BLOQUE_SIZE);
	indiceCotizacionesPorServicio = new ArbolBMas(sys_files_path+"IndiceCotizacionesPorServicio.dat", BLOQUE_SIZE);
	indiceCategorias = new ArbolBMas(sys_files_path+"IndiceCategorias.dat", BLOQUE_SIZE);

	indiceInvertidoServicios = new ListaInvertida(sys_files_path+"indiceInvertidoServicios.dat", BLOQUE_SIZE, STOPWORDS_PATH);
	indiceInvertidoConsultas = new ListaInvertida(sys_files_path+"indiceInvertidoConsultas.dat", BLOQUE_SIZE, STOPWORDS_PATH);
	indiceInvertidoCategorias = new ListaInvertida(sys_files_path+"indiceInvertidoCategorias.dat", BLOQUE_SIZE, STOPWORDS_PATH);

	indiceInvertidoServicios->setIdBase(ID_BASE_SERVICIOS);
	indiceInvertidoConsultas->setIdBase(ID_BASE_CONSULTAS);
	indiceInvertidoCategorias->setIdBase(ID_BASE_CATEGORIAS);

	encriptador = new Encriptador(CLAVE_UNICA);
	encriptadorCotizaciones = new Encriptador(CLAVE_UNICA);

	crearAdministradorPrincipal();
}

Sistema::~Sistema() {
	delete hashUsuarios;
	delete hashServicios;
	delete hashConsultas;
	delete hashCategorias;
	delete hashCotizaciones;
	delete indiceServiciosPorCategoria;
	delete indiceServiciosPorProveedor;
	delete indiceConsultasPorServicio;
	delete indiceCotizacionesPorServicio;
	delete indiceCategorias;
	delete indiceInvertidoServicios;
	delete indiceInvertidoConsultas;
	delete indiceInvertidoCategorias;
	delete encriptador;
	delete encriptadorCotizaciones;
}

char Sistema::loguearUsuario(int dni, string pass) {
	Usuario user;
	if(!passwordValida(pass))
		return 0;
	if(hashUsuarios->buscarRegistro(dni, &user)) {
		const char* aux = encriptador->ToUpper(pass).c_str();
		char* c = new char[pass.length() + 1];
		strcpy(c, aux);
		encriptador->encriptar(c, pass.length());
		if(user.getPassword() == c)
			return user.getTipo();
	}
	return 0;
}

bool Sistema::passwordValida(string nueva) {
	return encriptador->claveValida(nueva);
}

bool Sistema::registrarUsuario(Usuario& nuevo) {
	string pass = nuevo.getPassword();
	if(!passwordValida(pass))
		return false;
	const char* aux = encriptador->ToUpper(pass).c_str();
	char* cpass = new char[pass.length() + 1];
	strcpy(cpass, aux);
	encriptador->encriptar(cpass, pass.length());
	nuevo.setPassword(cpass);
	return hashUsuarios->agregarRegistro(&nuevo);
}

bool Sistema::recuperarUsuario(int dni, Usuario& recuperado) {
	return hashUsuarios->buscarRegistro(dni, &recuperado);
}

bool Sistema::modificarUsuario(Usuario& modificado) {
	return hashUsuarios->modificarRegistro(&modificado);
}

bool Sistema::eliminarUsuario(int dni) {
	return hashUsuarios->eliminarRegistro(dni);
}

void Sistema::consultarServiciosPorProveedor(int idProveedor, vector<Servicio>* resultado) {
	stringstream sstr;
	sstr << idProveedor;
	vector<RegistroArbol*> idsServicio;
	Servicio registro;
	idsServicio= indiceServiciosPorProveedor->busquedaMultiple(Clave(sstr.str()));
	for(unsigned int i =0; i < idsServicio.size(); i++){
		if(hashServicios->buscarRegistro(atoi(idsServicio[i]->getDatos()->toString().c_str()), &registro)){
			resultado->push_back(registro);
		}
	}
}

bool Sistema::consultarServicioPorId(int idServicio, Servicio& consultado) {
	return hashServicios->buscarRegistro(idServicio, &consultado);
}

void Sistema::consultarServiciosPorCategoria(string categoria, vector<Servicio>* resultado) {
	Clave clave = Clave(categoria);
	vector<RegistroArbol*> idsServicio;
	Servicio registro;
	idsServicio = indiceServiciosPorCategoria->busquedaMultiple(clave);
	for(unsigned int i =0; i < idsServicio.size(); i++){
		if(hashServicios->buscarRegistro(atoi(idsServicio[i]->getDatos()->toString().c_str()), &registro)){
			resultado->push_back(registro);
		}
	}
}

void Sistema::consultarServicioPorPalabraClave(string palabra, vector<Servicio>* resultado) {
	vector<int> ids;
	indiceInvertidoServicios->buscar(palabra, &ids);
	for(int i=0; i < (int)ids.size(); i++){
		Servicio servicio;
		if(hashServicios->buscarRegistro(ids[i], &servicio)){
			resultado->push_back(servicio);
		}
	}
}

bool Sistema::realizarConsulta(int idServicio, int idUsuario, string texto) {
	ArchivoDeBloques arch(sys_files_path+"consultas", BLOQUE_SIZE);

	Publicacion consulta;
	consulta.texto=texto;
	consulta.fecha=getFecha();
	consulta.hora=getHora();

	Consulta nueva(ID_BASE_CONSULTAS+arch.getCantidadRegistros()+1);
	nueva.setIdUsuario(idUsuario);
	nueva.setIdServicio(idServicio);
	nueva.setConsulta(consulta);
	nueva.ocultar(false);

	return crearConsulta(nueva);
}

bool Sistema::crearConsulta(Consulta& nueva) {
	stringstream str;
	str << nueva.getIdServicio();
	stringstream stream;
	stream << nueva.getId();
	Clave* clave = new Clave(str.str() + stream.str());
	CadenaBytes* cadena = new CadenaBytes(stream.str());
	RegistroArbol* registroArbol = new RegistroArbol(clave, cadena);
	indiceConsultasPorServicio->insertar(registroArbol);
	indiceInvertidoConsultas->extraerTerminos(nueva.getConstulta().texto, nueva.getId());
	return hashConsultas->agregarRegistro(&nueva);
}

bool Sistema::realizarPedidoDeCotizacion(int idServicio, int idUsuario, string texto) {
	ArchivoDeBloques arch(sys_files_path+"cotizaciones", BLOQUE_SIZE);

	Publicacion pedido;
	pedido.texto=texto;
	pedido.fecha=getFecha();
	pedido.hora=getHora();

	Cotizacion nueva(ID_BASE_COTIZACIONES+arch.getCantidadRegistros()+1);
	nueva.setIdUsuario(idUsuario);
	nueva.setIdServicio(idServicio);
	nueva.setPedido(pedido);

	return crearCotizacion(nueva);
}

bool Sistema::crearCotizacion(Cotizacion& nueva) {
	Servicio servicio;
	hashServicios->buscarRegistro(nueva.getIdServicio(), &servicio);
	Usuario proveedor;
	hashUsuarios->buscarRegistro(servicio.getIdProveedor(), &proveedor);
	string pass = proveedor.getPassword();
	const char* auxpass = pass.c_str();
	char* claveprov = new char[pass.length() + 1];
	strcpy(claveprov, auxpass);
	encriptador->desencriptar(claveprov, pass.length());
	encriptadorCotizaciones->setClave(claveprov);
	Publicacion pedido = nueva.getPedido();
	string texto = encriptador->normalizarTexto(pedido.texto);
	const char* aux = texto.c_str();
	char* cTexto = new char[texto.length() + 1];
	strcpy(cTexto, aux);
	encriptadorCotizaciones->encriptar(cTexto, texto.length());
	pedido.texto = cTexto;
	nueva.setPedido(pedido);
	stringstream str;
	str << nueva.getIdServicio();
	stringstream stream;
	stream << nueva.getId();
	Clave* clave = new Clave(str.str() + stream.str());
	CadenaBytes* cadena = new CadenaBytes(stream.str());
	RegistroArbol* registroArbol = new RegistroArbol(clave, cadena);
	indiceCotizacionesPorServicio->insertar(registroArbol);
	return hashCotizaciones->agregarRegistro(&nueva);
}

bool Sistema::registrarProveedor(int dni) {
	Usuario nuevoProveedor;
	if(hashUsuarios->buscarRegistro(dni, &nuevoProveedor)) {
		nuevoProveedor.setTipo('P');
		return modificarUsuario(nuevoProveedor);
	}
	return false;
}

int Sistema::getIdIncrementalDeServicios() {
	return ID_BASE_SERVICIOS+indiceServiciosPorProveedor->getUltimoIdIngresado()+1;
}

bool Sistema::crearServicio(Servicio& nuevo) {
	bool carga_hash = hashServicios->agregarRegistro(&nuevo);
	vector<int> categorias = nuevo.getCategorias();
	stringstream idServicio;
	idServicio << nuevo.getId();
	CadenaBytes* cadena = new CadenaBytes(idServicio.str());
	for (unsigned int i =0; i< categorias.size(); i++){
		Categoria categoria;
		if(hashCategorias->buscarRegistro(categorias[i], &categoria)){
			string nombreCategoria = categoria.getNombre();
			Clave* clave = new Clave(nombreCategoria + idServicio.str());
			indiceServiciosPorCategoria->insertar(new RegistroArbol(clave, cadena));
		}
	}
	stringstream idProveedor;
	idProveedor << nuevo.getIdProveedor();
	Clave* clave = new Clave(idProveedor.str() + idServicio.str());
	bool carga_arbol = indiceServiciosPorProveedor->insertar(new RegistroArbol(clave, cadena));
	indiceInvertidoServicios->extraerTerminos(nuevo.getDescripcion(), nuevo.getId());
	return carga_hash && carga_arbol;
}

void Sistema::listarCategorias(vector<Categoria>* lista) {
	IteradorArbolBMas* iterador = indiceCategorias->begin();
	while(iterador->hasNext()){
		RegistroArbol* registro = iterador->next();
		Categoria categoria;
		hashCategorias->buscarRegistro(atoi(registro->getClave()->getClave().c_str()), &categoria);
		lista->push_back(categoria);
	}
}

void Sistema::listarCategoriasPorPalabraClave(string palabra, vector<Categoria>* lista) {
	vector<int> ids;
	indiceInvertidoCategorias->buscar(palabra, &ids);
	for(int i=0; i < (int)ids.size(); i++){
		Categoria categoria;
		if(hashCategorias->buscarRegistro(ids[i], &categoria)){
			lista->push_back(categoria);
		}
	}
}

bool Sistema::asociarServicioACategoria(int idServicio, int idCategoria) {
	Servicio servicio;
	hashServicios->buscarRegistro(idServicio, &servicio);
	servicio.addCategoria(idCategoria);
	hashServicios->modificarRegistro(&servicio);
	Categoria categoria;
	stringstream idServ;
	idServ << idServicio;
	if(hashCategorias->buscarRegistro(idCategoria, &categoria)){
		string nombreCategoria = categoria.getNombre();
		Clave* clave = new Clave(nombreCategoria + idServ.str());
		CadenaBytes* cadena = new CadenaBytes(idServ.str());
		indiceServiciosPorCategoria->insertar(new RegistroArbol(clave, cadena));
	}
	return true;
}

void Sistema::listarConsultasPorServicio(int idServicio, vector<Consulta>* lista) {
	stringstream sstr;
	sstr << idServicio;
	Clave clave = Clave(sstr.str());
	vector<RegistroArbol*> idsConsultas;
	Consulta registro;
	idsConsultas = indiceConsultasPorServicio->busquedaMultiple(clave);
	for(unsigned int i =0; i < idsConsultas.size(); i++){
		if(hashConsultas->buscarRegistro(atoi(idsConsultas[i]->getDatos()->toString().c_str()), &registro)){
			lista->push_back(registro);
		}
	}
}

bool Sistema::responderConsulta(int idConsulta, string texto) {
	Consulta consulta;
	if(hashConsultas->buscarRegistro(idConsulta, &consulta)) {
		Publicacion respuesta;
		respuesta.texto=texto;
		respuesta.fecha=getFecha();
		respuesta.hora=getHora();
		consulta.setRespuesta(respuesta);
		return hashConsultas->modificarRegistro(&consulta);
	}
	return false;
}

bool Sistema::eliminarServicio(int idServicio) {
	return hashServicios->eliminarRegistro(idServicio);
}

void Sistema::listarCotizacionesPorServicio(int idServicio, vector<Cotizacion>* lista) {
	stringstream sstr;
	sstr << idServicio;
	Clave clave = Clave(sstr.str());
	vector<RegistroArbol*> idsConsultas;
	Cotizacion registro;
	idsConsultas = indiceCotizacionesPorServicio->busquedaMultiple(clave);
	for(unsigned int i =0; i < idsConsultas.size(); i++){
		if(hashCotizaciones->buscarRegistro(atoi(idsConsultas[i]->getDatos()->toString().c_str()), &registro)){
			Servicio servicio;
			hashServicios->buscarRegistro(idServicio, &servicio);
			Usuario proveedor;
			hashUsuarios->buscarRegistro(servicio.getIdProveedor(), &proveedor);
			string pass = proveedor.getPassword();
			const char* auxpass = pass.c_str();
			char* clave = new char[pass.length() + 1];
			strcpy(clave, auxpass);
			encriptador->desencriptar(clave, pass.length());
			encriptadorCotizaciones->setClave(clave);
			const char* texto = registro.getPedido().texto.c_str();
			char* auxtexto = new char[registro.getPedido().texto.length() + 1];
			strcpy(auxtexto, texto);
			encriptadorCotizaciones->desencriptar(auxtexto, registro.getPedido().texto.length());
			registro.getPedido().texto = auxtexto;
			lista->push_back(registro);
		}
	}
}

bool Sistema::crearAdministradorPrincipal() {
	Usuario adminPpal(1111111,"admin","ppal",'A');
	adminPpal.setPassword("root");
	return registrarUsuario(adminPpal);
}

bool Sistema::registrarAdministrador(int dni) {
	Usuario nuevoAdimistrador;
	if(hashUsuarios->buscarRegistro(dni, &nuevoAdimistrador)) {
		nuevoAdimistrador.setTipo('A');
		return modificarUsuario(nuevoAdimistrador);
	}
	return false;
}

int Sistema::getIdIncrementalDeCategorias() {
	return ID_BASE_CATEGORIAS+indiceCategorias->getUltimoIdIngresado()+1;
}

bool Sistema::crearCategoria(Categoria& nueva) {
	hashCategorias->agregarRegistro(&nueva);
	stringstream idCategoria;
	idCategoria << nueva.getId();
	Clave* clave = new Clave(idCategoria.str());
	CadenaBytes* cadena = new CadenaBytes(idCategoria.str());
	indiceCategorias->insertar(new RegistroArbol(clave, cadena));
	indiceInvertidoCategorias->extraerTerminos(nueva.getDescripcion(), nueva.getId());
	return true;
}

bool Sistema::crearCategorias(string archivoCategorias) {
	ifstream f(archivoCategorias.c_str());
	bool archivoExiste = f.good();
	f.close();
	if(archivoExiste)
		Cargar_Archivo::cargar_categorias(*this, archivoCategorias, false);
	return archivoExiste;
}

bool Sistema::recuperarCategoria(int idCategoria, Categoria& recuperada) {
	return hashCategorias->buscarRegistro(idCategoria, &recuperada);
}

bool Sistema::modificarCategoria(Categoria& modificada) {
	return hashCategorias->modificarRegistro(&modificada);
}

bool Sistema::eliminarCategoria(int idCategoria) {
	return hashCategorias->eliminarRegistro(idCategoria);
}

void Sistema::listarConsultas(vector<Consulta>* lista) {
	IteradorArbolBMas* iterador = indiceConsultasPorServicio->begin();
	while(iterador->hasNext()){
		RegistroArbol* registro = iterador->next();
		Consulta consulta;
		hashConsultas->buscarRegistro(atoi(registro->getDatos()->toString().c_str()), &consulta);
		lista->push_back(consulta);
	}
}

void Sistema::listarConsultasPorPalabraClave(string palabra, vector<Consulta>* lista) {
	vector<int> ids;
	indiceInvertidoConsultas->buscar(palabra, &ids);
	for(int i=0; i < (int)ids.size(); i++){
		Consulta consulta;
		if(hashConsultas->buscarRegistro(ids[i], &consulta)){
			lista->push_back(consulta);
		}
	}
}

bool Sistema::moderarConsultas(int idConsulta, bool mostrar) {
	Consulta consulta;
	if(hashConsultas->buscarRegistro(idConsulta, &consulta)) {
		consulta.ocultar(mostrar);
		return hashConsultas->modificarRegistro(&consulta);
	}
	return false;
}

string Sistema::recuperarPassword(int dni) {
	Usuario usuario;
	if(!recuperarUsuario(dni, usuario))
		return "";
	// se desencripta la contraseña
	string pass = usuario.getPassword();
	const char* auxpass = pass.c_str();
	char* clave = new char[pass.length() + 1];
	strcpy(clave, auxpass);
	encriptador->desencriptar(clave, pass.length());
	usuario.setPassword(clave);
	return usuario.getPassword();
}

bool Sistema::modificarPassword(int dni, string pass) {
	if(!passwordValida(pass))
		return false;
	Usuario usuario;
	if(hashUsuarios->buscarRegistro(dni, &usuario)) {
		// se encripta la contraseña antes de guardarla
		const char* aux = encriptador->ToUpper(pass).c_str();
		char* cpass = new char[pass.length() + 1];
		strcpy(cpass, aux);
		encriptador->encriptar(cpass, pass.length());
		usuario.setPassword(cpass);
		return modificarUsuario(usuario);
	}
	return false;
}

string Sistema::getFecha() {
	time_t t = time(0);
	struct tm * now = localtime( & t );
	std::stringstream fecha;
	fecha << now->tm_year + 1900;
	fecha << setfill('0') << setw(2) << now->tm_mon + 1;
	fecha << setfill('0') << setw(2) << now->tm_mday;
	return fecha.str();
}

string Sistema::getHora() {
	time_t t = time(0);
	struct tm * now = localtime( & t );
	std::stringstream hora;
	hora << setfill('0') << setw(2) << now->tm_hour;
	hora << setfill('0') << setw(2) << now->tm_min;
	return hora.str();
}
